﻿using System;
using IO = System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
using System.Diagnostics;
using MassRenamer.Properties;
using System.IO;

namespace MassRenamer
{
	/// <summary>
	/// Interaction logic for Window1.xaml
	/// </summary>
	public partial class Window1 : Window
	{
		/// <summary>
		/// The current directory being looked at
		/// </summary>
		private string currentDirectory;

		/// <summary>
		/// The files in the current directory
		/// </summary>
		private ObservableCollection<string> files;

		/// <summary>
		/// The collection of previews
		/// </summary>
		private ObservableCollection<string> previews;

		
		public Window1()
		{
			InitializeComponent();
			files = new ObservableCollection<string>();
			previews = new ObservableCollection<string>();
			currentDirectory = "";

			//Set the list box data contexes
			fileListBox.DataContext = files;
			previewListBox.DataContext = previews;

			//Add numeric Up Down value change listeners
			trimEndNumericUpDown.ValueChanged += numericValueChangedGeneratePreview;
			trimStartNumericUpDown.ValueChanged += numericValueChangedGeneratePreview;
			insertNumericUpDown.ValueChanged += numericValueChangedGeneratePreview;
			rangeRemoveStartNumericUpDown.ValueChanged += RangeRemoveNumericUpDownValueChanged;
			rangeRemoveEndNumericUpDown.ValueChanged += RangeRemoveNumericUpDownValueChanged;

			//Set up label listeners
			replaceDashesLabel.MouseDown += (object sender, MouseButtonEventArgs e) =>
				{
					replaceDashesCheckBox.IsChecked = !replaceDashesCheckBox.IsChecked;
					GeneratePreviews();
				};

			replaceUnderscoresLabel.MouseDown += (object sender, MouseButtonEventArgs e) =>
				{
					replaceUnderscoresCheckBox.IsChecked = !replaceUnderscoresCheckBox.IsChecked;
					GeneratePreviews();
				};

			capitalizeLabel.MouseDown += (object sender, MouseButtonEventArgs e) =>
				{
					capitalizeCheckBox.IsChecked = !capitalizeCheckBox.IsChecked;
					GeneratePreviews();
				};

			trimALabel.MouseDown += (object sender, MouseButtonEventArgs e) =>
				{
					trimACheckBox.IsChecked = !trimACheckBox.IsChecked;
					GeneratePreviews();
				};

			trimTheLabel.MouseDown += (object sender, MouseButtonEventArgs e) =>
				{
					trimTheCheckBox.IsChecked = !trimTheCheckBox.IsChecked;
					GeneratePreviews();
				};
			toLowerLabel.MouseDown += (object sender, MouseButtonEventArgs e) =>
			{
				toLowerCheckBox.IsChecked = !toLowerCheckBox.IsChecked;
				GeneratePreviews();
			};


			Height = Settings.Default.WindowHeight;
			Width = Settings.Default.WindowWidth;

			//Set whether to show full file paths or just names
			showFullPathMenuItem.IsChecked = Settings.Default.ShowFullFilePath;
			SetFileTemplate();
		}

		/// <summary>
		/// Checks what the ShowFullFilePath setting is and set the proper data template
		/// for the file list box and preview list box
		/// </summary>
		private void SetFileTemplate()
		{
			if (Settings.Default.ShowFullFilePath)
			{
				fileListBox.ItemTemplate = (DataTemplate)FindResource("fileWithPathDataTemplate");
				previewListBox.ItemTemplate = (DataTemplate)Resources["fileWithPathDataTemplate"];
			}
			else
			{
				fileListBox.ItemTemplate = (DataTemplate)FindResource("fileWithoutPathDataTemplate");
				previewListBox.ItemTemplate = (DataTemplate)Resources["fileWithoutPathDataTemplate"];
			}
		}

		/// <summary>
		/// Tell if the passed path is a directory or file
		/// </summary>
		/// <param name="path">The path to test</param>
		/// <returns>True if it is a directory, false if not</returns>
		private bool IsDirectory(string path)
		{
			// get the file attributes for file or directory
			FileAttributes attr = File.GetAttributes(path);

			//detect whether its a directory or file
			if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
				return true;

			return false;
		}

		/// <summary>
		/// Appends the passed files to the currently monitored files.
		/// If one of the passed files is a directory it will add its files to the file list
		/// </summary>
		/// <param name="files">The files to append</param>
		/// <remarks>When done this generates previews</remarks>
		private void AppendFilesToCurrentFiles(string[] newFiles)
		{
			foreach (string file in newFiles)
			{
					if (IsDirectory(file))
					{
						foreach (string subFile in IO.Directory.GetFiles(file))
						{
							if (!IsDirectory(subFile))
								AppendFileToCurrentFiles(subFile);
						}
					}
					else
						AppendFileToCurrentFiles(file);
			}
			GeneratePreviews();
		}

		/// <summary>
		/// Appends the passed file to the currently monitored files, if it is
		/// not already in the files
		/// </summary>
		/// <param name="file">The file to append</param>
		/// <remarks>file should not be a directory</remarks>
		private void AppendFileToCurrentFiles(string file)
		{
			if (!files.Contains(file))
				files.Add(file);
		}


		/// <summary>
		/// Removes the collection of old files from the list being monitored
		/// </summary>
		/// <param name="oldFiles">The collection of files to remove</param>
		/// <remarks>When done this generates previews</remarks>
		private void RemoveFiles(IList<string> oldFiles)
		{
			foreach (string oldFile in oldFiles)
				files.Remove(oldFile);

			GeneratePreviews();
		}

		/// <summary>
		/// Removes all selected items that are currently selected in 
		/// the file list box
		/// </summary>
		private void RemoveSelectedItemsInFileListbox()
		{
			List<string> selectedFiles = new List<string>();
			foreach (object selectedItem in fileListBox.SelectedItems)
				selectedFiles.Add(selectedItem.ToString());

			RemoveFiles(selectedFiles);
		}

		

		/// <summary>
		/// Load the files in the current directory
		/// </summary>
		private void LoadCurrentDirectory()
		{
			files.Clear();
			foreach (string file in IO.Directory.GetFiles(currentDirectory))
				files.Add(file);

			GeneratePreviews();
		}

		/// <summary>
		/// Rename the files based on the specified actions
		/// </summary>
		private void RenameFiles()
		{
			string newFileName = "";
			foreach (string file in files)
			{
				newFileName = RenameFile(file);
				//Actually rename the file if it changed
				if (!newFileName.Equals(file))
				{
					IO.FileInfo info = new IO.FileInfo(file);
					info.MoveTo(newFileName);
				}
			}
			LoadCurrentDirectory();
		}

		/// <summary>
		/// Generate the File Previews
		/// </summary>
		private void GeneratePreviews()
		{
			previews.Clear();
			foreach(string file in files)
			{
				previews.Add(RenameFile(file));
			}
		}

		/// <summary>
		/// Takes in a file and changes the name based on what the user has 
		/// specified
		/// </summary>
		/// <param name="file">The original file name</param>
		/// <returns>The renamed file name</returns>
		private string RenameFile(string file)
		{
			Debug.Assert(!string.IsNullOrEmpty(file), "Passed file was null or \"\"");

			string newFileName = IO.Path.GetFileNameWithoutExtension(file);

			if ((bool)replaceCheckBox.IsChecked)
				newFileName = WildcardReplace(newFileName);

			if ((bool)trimStartCheckBox.IsChecked)
				newFileName = TrimStart(newFileName);

			if ((bool)trimEndCheckBox.IsChecked)
				newFileName = TrimEnd(newFileName);

			if ((bool)replaceDashesCheckBox.IsChecked)
				newFileName = ReplaceDashesWithSpaceDashSpace(newFileName);

			if ((bool)replaceUnderscoresCheckBox.IsChecked)
				newFileName = ReplaceUnderScores(newFileName);

			if ((bool)trimTheCheckBox.IsChecked)
				newFileName = TrimThe(newFileName);

			if ((bool)trimACheckBox.IsChecked)
				newFileName = TrimA(newFileName);

			if ((bool)capitalizeCheckBox.IsChecked)
				newFileName = Capitalize(newFileName);

			if ((bool)insertCheckBox.IsChecked)
				newFileName = InsertString(newFileName);

			if ((bool)rangeRemoveCheckBox.IsChecked)
				newFileName = RemoveRange(newFileName);

			if ((bool)toLowerCheckBox.IsChecked)
				newFileName = ToLowerCase(newFileName);

			return IO.Path.GetDirectoryName(file) + IO.Path.DirectorySeparatorChar +
						newFileName + IO.Path.GetExtension(file);
		}



		#region replace actions

		/// <summary>
		/// Get the regular expression to use for the replace
		/// </summary>
		private string ReplaceRegex
		{
			get
			{
				return Regex.Escape(replaceBeforeTextBox.Text).Replace("%", ".*");
			}
		}

		/// <summary>
		/// Get the substituion string to use in the replace action
		/// </summary>
		private string ReplaceSubstituion
		{
			get
			{
				return replaceToTextBox.Text;
			}
		}

		/// <summary>
		/// Perform the wild card replace action on the passed string
		/// </summary>
		/// <param name="original">The string to perform the replace action on</param>
		/// <returns>The new string with the replacements done</returns>
		private string WildcardReplace(string original)
		{
			Regex rex = new Regex(ReplaceRegex);
			return rex.Replace(original, ReplaceSubstituion);
		}

		/// <summary>
		/// Replace all underscores with a spaces
		/// </summary>
		/// <param name="original">The string to perform this replace action on</param>
		/// <returns>The new string with all underscores replaced with spaces</returns>
		private string ReplaceUnderScores(string original)
		{
			return original.Replace("_", " ");
		}

		/// <summary>
		/// Replace all dashes with a space dash space.
		/// </summary>
		/// <example>
		/// "-" becomes " - "
		/// </example>
		/// <param name="original">The string to perform the replace action on</param>
		/// <returns>The new string with all dashes replaced with space dash space</returns>
		private string ReplaceDashesWithSpaceDashSpace(string original)
		{
			//If it does not contain a character-character just return the 
			//original string
			Regex rex = new Regex(@"\S-\S");
			if(!rex.IsMatch(original))
			{
				return original;
			}

			string[] tokens = original.Split(( '-' ));

			string newName = "";

			foreach (string token in tokens)
			{
				//If there is a non space at start add a space
				if (!token[0].Equals(' '))
					newName += " ";

				newName += token;

				//If there is a non space at end add a space, only if it is not the last token
				if (!token[token.Length - 1].Equals(' ') && !tokens[tokens.Length-1].Equals(token) )
					newName += " ";
				newName += "-";
			}

			//Remove the added space and that last dash and last space
			newName = newName.Substring(1, newName.Length - 2);
			return newName;
		}

		#endregion

		/// <summary>
		/// Replace all noncaptialized first letters with a capitalized letter
		/// </summary>
		/// <example>
		/// hello world.wmv becomes Hello World.wmv
		/// </example>
		/// <param name="original">The string to perform the captialization action on</param>
		/// <returns>The new string with all starting letters capitalized</returns>
		private string Capitalize(string original)
		{
			//Took this out as it won't captilize one word names, leaving in as
			//comments incase it is found out this breaks in another case
			////if it contains no spaces just return original as nothing will be done
			//if (!original.Contains(' '))
			//{
			//    return original;
			//}

			string[] tokens = original.Split((' '));

			string newName = "";
			string token = "";
			string firstLetter = "";
			Regex rex = new Regex("[a-z]");
			for (int i = 0; i < tokens.Length; i++)
			{
				token = tokens[i];
				if (!token.Equals(""))
				{
					firstLetter = token[0].ToString(); ;
					if (rex.IsMatch(firstLetter))
					{
						token = firstLetter.ToUpper() + token.Substring(1, token.Length - 1);
					}
				}
				//Add the spaces back in from the split
				newName += " " + token;
			}
			//Remove the first added space
			newName = newName.Substring(1, newName.Length - 1);
			return newName;
		}

		/// <summary>
		/// Makes all characters in the passed string lowercase
		/// </summary>
		/// <param name="original">The original string to force into lower case</param>
		/// <returns>The lower case version of the original string</returns>
		private string ToLowerCase(string original)
		{
			return original.ToLower();
		}

		

		#region insert actions

		/// <summary>
		/// The index to perform the insert action at
		/// </summary>
		private int InsertIndex { get { return (int)insertNumericUpDown.Value; } }

		/// <summary>
		/// The add in string for the insert action
		/// </summary>
		private string InsertAddin { get { return insertTextBox.Text; } }

		/// <summary>
		/// Insert a string into the original string
		/// </summary>
		/// <param name="original">The original string to insert into</param>
		/// <returns>The original with addin added at index</returns>
		private string InsertString(string original)
		{
			Debug.Assert(InsertIndex >= 0, "InsertString: InsertIndex was negative");
			int index = InsertIndex;
			if (index > original.Length)
				index = original.Length;

			return original.Insert(index, InsertAddin);

		}

		#endregion

		#region untested range actions

		/// <summary>
		/// The starting index of range removal
		/// </summary>
		private int StartRangeRemove { get { return (int)rangeRemoveStartNumericUpDown.Value; } }

		/// <summary>
		/// The ending index of range removal
		/// </summary>
		private int EndRangeRemove { get { return (int)rangeRemoveEndNumericUpDown.Value; } }

		/// <summary>
		/// Listens for when a range removal numeric up down changes and makes sure 
		/// start and end do not overlap, generates a preview if values are okay
		/// </summary>
		/// <param name="sender">The sender of this event</param>
		/// <param name="e">Not Used</param>
		private void RangeRemoveNumericUpDownValueChanged(object sender, EventArgs e)
		{

			if (StartRangeRemove >= EndRangeRemove)
			{
				//Ranges overlap
				if (sender.Equals(rangeRemoveStartNumericUpDown))
				{
					rangeRemoveEndNumericUpDown.Value = StartRangeRemove + 1;
				}

				if (sender.Equals(rangeRemoveEndNumericUpDown))
				{
					rangeRemoveStartNumericUpDown.Value = EndRangeRemove - 1;
				}

				Debug.Assert(false, "RangeRemoveNumericUpDownValueChanged called " +
									"when sender was not start nor end numeric up down");
			}
			else
			{
				GeneratePreviews();
			}
		}

		/// <summary>
		/// Remove a range of characters from a string
		/// </summary>
		/// <param name="original">The original string</param>
		/// <param name="start">The start of the range to remove</param>
		/// <param name="end">The end of the range to remove</param>
		/// <returns>The original with the range from start to end removed</returns>
		private string RemoveRange(string original)
		{
			Debug.Assert(StartRangeRemove >= 0, "RemoveRange: Start was negative");

			int end = EndRangeRemove;
			int start = StartRangeRemove;

			if (end > original.Length)
				end = original.Length;
			if (start > end)
				start = end;

			return original.Remove(start, end - start);
		}

		#endregion


		#region trim actions

		/// <summary>
		/// Trim the word "The" or "the" from the beginning of the string
		/// </summary>
		/// <param name="original">The original string to have the "The" removed from</param>
		/// <returns>The string with the "The" trimmed from the start</returns>
		private string TrimThe(string original)
		{
			string lowercase = original.ToLower();
			if (!lowercase.Contains("the") || lowercase.Length <= 3)
				return original;

			return original.Substring(3, original.Length - 3).TrimStart();
		}

		/// <summary>
		/// Trim the word "A" or "a" from the beginning of the string
		/// </summary>
		/// <param name="original">The original string to have the "A" removed from</param>
		/// <returns>The string with the "A" trimmed from the start</returns>
		private string TrimA(string original)
		{
			if (original[0].Equals('a') || original[0].Equals('A'))
				return original.Substring(1, original.Length - 1).TrimStart();

			return original;
		}

		/// <summary>
		/// Get the number of places to trim the start by
		/// </summary>
		private int TrimStartNumber
		{
			get { return (int)trimStartNumericUpDown.Value; }
		}

		/// <summary>
		/// Get the number of places to trim the end by
		/// </summary>
		private int TrimEndNumber
		{
			get { return (int)trimEndNumericUpDown.Value; }
		}

		/// <summary>
		/// Perform the trim start action
		/// </summary>
		/// <param name="original">The string to trim</param>
		/// <returns>The changed string with its start trimmed.</returns>
		/// <remarks>If TrimStartNumber is greater than size nothing will happen</remarks>
		private string TrimStart(string original)
		{
			//If trimming the whole thing just return nothing
			if (TrimStartNumber >= original.Length)
				return original;

			return original.Substring(TrimStartNumber);
		}

		/// <summary>
		/// Perform the trim end action
		/// </summary>
		/// <param name="original">The string to trim</param>
		/// <returns>The changed string with its end trimmed.</returns>
		/// <remarks>If TrimEndNumber is greater than size nothing will happen</remarks>
		private string TrimEnd(string original)
		{
			//If trimming the whole thing just return nothing
			if (TrimEndNumber >= original.Length)
				return original;

			return original.Substring(0, original.Length - TrimEndNumber);
		}

		#endregion

		#region button listeners

		/// <summary>
		/// Listens for when the loadDirectory Button is clicked, brings up a 
		/// directory dialog and calls load directory
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void loadDirectoryButton_Click(object sender, RoutedEventArgs e)
		{
			System.Windows.Forms.FolderBrowserDialog folderDialog = 
				new System.Windows.Forms.FolderBrowserDialog();
			if (folderDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				currentDirectory = folderDialog.SelectedPath;
				LoadCurrentDirectory();
			}
		}

		
		/// <summary>
		/// Listens for when the apply button is clicked and calls ApplyRename
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void applyButton_Click(object sender, RoutedEventArgs e)
		{
			RenameFiles();
		}

		#endregion

		#region preview generating events

		/// <summary>
		/// Listens for when check boxes are clicked and generates previews
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void checkBoxClickGeneratePreview(object sender, RoutedEventArgs e)
		{
			GeneratePreviews();
		}

		/// <summary>
		/// Listens for when text changes in a text box and generates previews
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void textChangeGeneratePreview(object sender, TextChangedEventArgs e)
		{
			GeneratePreviews();
		}

		/// <summary>
		/// Listens for when numeric up down's value changes and generates preview
		/// </summary>
		/// <param name="sender">Not Used</param>
		/// <param name="e">Not Used</param>
		private void numericValueChangedGeneratePreview(object sender, EventArgs e)
		{
			GeneratePreviews();
		}

		#endregion


		/// <summary>
		/// Listens for when the window is closed and saves the last height and
		/// width
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			Settings.Default.WindowHeight = this.Height;
			Settings.Default.WindowWidth = this.Width;
			Settings.Default.Save();
		}


		#region drag into file listing functionality


		/// <summary>
		/// Listens for when an item is dragged into file list box, provides visual
		/// cues that it can accept the drag
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void fileListBox_DragEnter(object sender, DragEventArgs e)
		{
			fileListBox.Background = Brushes.LightBlue;	
		}

		/// <summary>
		/// Listens for when an item is dragged over file list box and changes the drag
		/// effect as to whether it can handle teh dragged item or not
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Arguments about the dragged item</param>
		private void fileListBox_DragOver(object sender, DragEventArgs e)
		{
			if (e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				
				e.Effects = DragDropEffects.Copy;
			}
			else
			{
				e.Effects = DragDropEffects.None;
			}
		}

		/// <summary>
		/// Listens for when an item being dragged leaves the file list box and provides
		/// the visual cues.
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void fileListBox_DragLeave(object sender, DragEventArgs e)
		{
			fileListBox.Background = Brushes.White;
		}

		/// <summary>
		/// Listens for when an item is dropped into the file list box and calls AddToFiles
		/// if the dragged items are acceptable
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">The arguments for a drag drop event</param>
		private void fileListBox_Drop(object sender, DragEventArgs e)
		{
			fileListBox.Background = Brushes.White;

			if(e.Data.GetDataPresent(DataFormats.FileDrop))
			{
				string[] droppedFiles = e.Data.GetData(DataFormats.FileDrop) as string[];
				if (droppedFiles != null)
				{
					foreach (string file in droppedFiles)
						AppendFilesToCurrentFiles(droppedFiles);  
				}
			}
		}

		#endregion

		#region remove selected file listeners

		/// <summary>
		/// Listens for when a key up happens on file list box. If the key is delete
		/// it will call remove file.
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e"></param>
		private void fileListBox_KeyUp(object sender, KeyEventArgs e)
		{
			if (e.Key == Key.Delete)
				RemoveSelectedItemsInFileListbox();
		}

		/// <summary>
		/// Listens for when remove selected Menu Item is clicked and removes the selected 
		/// Menu item
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not Used</param>
		private void removeSelectedFilesMenuItem_Click(object sender, RoutedEventArgs e)
		{
			RemoveSelectedItemsInFileListbox();
		}

		#endregion

		

		/// <summary>
		/// Listens for when show full path menu item is clicked and sets the ShowFullFilePath
		/// Setting and tehn SetFileTemplate
		/// </summary>
		/// <param name="sender">Not used</param>
		/// <param name="e">Not used</param>
		private void showFullPathMenuItem_Click(object sender, RoutedEventArgs e)
		{
			Settings.Default.ShowFullFilePath = (bool)showFullPathMenuItem.IsChecked;
			SetFileTemplate();
		}



	}
}
